package fr.jhaccoun.demenager_eco.utilisateur;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.Size;

import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import fr.jhaccoun.demenager_eco.produit.Produit;

//TODO : Voir si plus tard possible de etendre directement l'implementation User de UserDetails au lieu d'implementer UserDetails (voir si interet)

//TODO : rajouter test unitaire sur les entités (validation des champs)

@Component
@Entity
@Table(name = "UTILISATEUR",uniqueConstraints={@UniqueConstraint(columnNames={"email"})})
public class Utilisateur implements UserDetails{
 
	private static final long serialVersionUID = 1L;

	public static final int TAILLE_PASSWORD_MIN = 8;
	public static final String PASSWORD_REGEXP = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!?+<>=$%@/*]).{"
			+ TAILLE_PASSWORD_MIN + ",})";
	
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="utilisateur_utilisateur_id_seq")
    @SequenceGenerator(name="utilisateur_utilisateur_id_seq", sequenceName="utilisateur_utilisateur_id_seq", allocationSize=1)
	@Column(name = "UTILISATEUR_ID")
	private Long id;
	
	@Temporal(TemporalType.TIMESTAMP)
	@Column(name = "DATE_MODIFICATION")
	private Date dateModification;
	
	@Temporal(TemporalType.TIMESTAMP)
	@Column(name = "DATE_CREATION")
	private Date dateCreation;
	
	@Size(max = 255)
	@Column(name = "EMAIL")  
	@Email
	@NotBlank
	private String email;
	 
	@Column(name = "PASSWORD")
	@NotBlank(groups={UserCreationModificationPasswordValidatorGroup.class})
	private String password;

	@Size(max = 60 )
	@NotBlank
	@Column(name = "PRENOM")
	private String prenom;
	
	@Size(max = 100)
	@NotBlank
	@Column(name = "NOM")
	private String nom;

	@Column(name = "ACTIF")
	private Boolean actif = true;
	
	@OneToMany( cascade = CascadeType.ALL, mappedBy = "utilisateur")
	private List<Produit> produits = new ArrayList<Produit>();
	
	 @ManyToMany(fetch = FetchType.EAGER)
	 @JoinTable(
	      name="UTILISATEUR_ROLE",
	      joinColumns={@JoinColumn(name="UTILISATEUR_ID", referencedColumnName="UTILISATEUR_ID")},
	      inverseJoinColumns={@JoinColumn(name="UTILISATEUR_ROLE_ID", referencedColumnName="ROLE_ID")})
	private Set<Role> roles = new HashSet<Role>();
 
	//TODO ajouter un constructeur prennant en parametre les champs obligatoires
	 
	@PrePersist
	public void beforeCreate() {
		setDateCreation(new Date());
		setDateModification(new Date());
	}
	
	@PreUpdate
	public void beforeUpdate() {
		setDateModification(new Date());
	}
	  
	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public Date getDateModification() {
		return dateModification;
	}

	public void setDateModification(Date dateModification) {
		this.dateModification = dateModification;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getPrenom() {
		return prenom;
	}

	public void setPrenom(String prenom) {
		this.prenom = prenom;
	}

	public String getNom() {
		return nom;
	}

	public void setNom(String nom) {
		this.nom = nom;
	}
	
	
	public Date getDateCreation() {
		return dateCreation;
	}

	public void setDateCreation(Date dateCreation) {
		this.dateCreation = dateCreation;
	}

	public Boolean getActif() {
		return actif;
	}

	public void setActif(Boolean actif) {
		this.actif = actif;
	}

	public Set<Role> getRoles() {
		return roles;
	}

	public void setRoles(Set<Role> roles) {
		this.roles = roles;
	}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
		authorities.addAll(roles);
		return authorities;
	}

	//TODO : Voir si moyen de preciser l'authentification par email et non par username dans spring security car la pr pallier ce pb on considére que usename = email...
	@Override
	public String getUsername() {
		return email;
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return actif;
	} 
	
	/**
	 * Vérifie qu'un mot de passe contient au moins une lettre, un chiffre et un caractère spécial
	 *  
	 */
	public static boolean isPasswordNonCrypteValide(String password) {
		Validate.notEmpty(password);
		
		Pattern p = Pattern.compile(PASSWORD_REGEXP);
		Matcher m = p.matcher(password);
		if (!m.find()) {
			return false;
		} 
		return true;
	}
	
	
	public void addProduit(Produit produit) {
		Validate.notNull(produit);
		produits.add(produit);
		produit.setUtilisateur(this);
	}
	
	public List<Produit> getProduits() {
		return produits;
	}

	public void setProduits(List<Produit> produits) {
		this.produits = produits;
	}

	@Override
	public String toString() { 
		return ReflectionToStringBuilder.toStringExclude(this,new String []{"produits","roles"}); 
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((email == null) ? 0 : email.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Utilisateur other = (Utilisateur) obj;
		if (email == null) {
			if (other.email != null)
				return false;
		} else if (!email.equals(other.email))
			return false;
		return true;
	}
	
	
	
}
